#include "LogToFiles.h"

#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

LogToFiles::LogToFiles ()
: log_handle_ (NDK_INVALID_HANDLE)
, current_file_size_ (0)
, file_name_ (0)
, log_dir_ (0)
, log_strategy_ (0)
, old_log_strategy_ (0)
{
    file_name_ = new char[NDK_MAX_FILENAME_LEN + 1];
	log_dir_   = new char[NDK_MAX_FILENAME_LEN + 1];
	memset (file_name_, '\0', NDK_MAX_FILENAME_LEN + 1);
#ifdef NDK_LOG_DEBUG
	fprintf (stderr, "Construct LogToFiles\n");
#endif
}
LogToFiles::~LogToFiles ()
{
	if (this->file_name_)
		delete []this->file_name_;
	this->file_name_ = 0;

	if (this->log_dir_)
		delete []this->log_dir_;
	this->log_dir_ = 0;

	if (this->log_strategy_)
		delete this->log_strategy_;
	this->log_strategy_ = 0;

	this->close ();
}
int LogToFiles::open (const char *logger_arg, 
		LogManager::Strategy *s)
{
	if (logger_arg == 0 || s == 0)
		return -1;

	// opened.
	if (this->log_strategy_) return -1;

	// Save strategy.
	this->log_strategy_ = new LogManager::Strategy;
	*this->log_strategy_ = *s;
	strncpy (this->log_dir_, logger_arg, NDK_MAX_FILENAME_LEN);

	this->last_calc_dtime_.update ();
	// Build file name
	this->build_filename ();

	// Precalculate the time of the next file.
	this->calc_next_time ();

	return this->create_file ();
}
int LogToFiles::create_file (void)
{
	// Check and create dir
	if (NDK_OS::mkdir (this->log_dir_, 0755) == -1)
	{
		if (errno != EEXIST) return -1;
		errno = 0;
	}
	// Get file size. If the file is exsit and its size is out of 
	// configure, then create a new one
	struct stat bf;
	memset (&bf, 0, sizeof(bf));
	int create_new_file = 1;
	if (::stat (this->file_name_, &bf) == 0)
	{
		this->current_file_size_ = bf.st_size;
		if (this->check_file_size ())
			create_new_file = 0;
	}
	// Open file
	if (create_new_file)
	{
		this->log_handle_ 
			= ::open (this->file_name_, O_CREAT | O_RDWR | O_APPEND, 0644);
		if (this->log_handle_ == NDK_INVALID_HANDLE) // create file failed
		{
			fprintf (stderr, "Open file failed : %s\n", 
					strerror (errno));
			return -1;
		}
	}
	return 0;
}
void LogToFiles::calc_next_time (void)
{
	if (this->old_log_strategy_ &&
			(
			 (this->log_strategy_->generate_file_period_
			  == this->old_log_strategy_->generate_file_period_)
			 && 
			 (this->log_strategy_->period_count_
			  == this->old_log_strategy_->period_count_)
			)
	   )
		return ;
	// Calculate next day start time 00:00:00.
	// 1. weekly
	if (this->log_strategy_->generate_file_period_ == GP_WEEKLY)
	{
		this->last_calc_dtime_.mday (this->last_calc_dtime_.mday () +
				7 * this->log_strategy_->period_count_);
		this->last_calc_dtime_.hour (0);
	}else
	{
		// 2. daily
		if (this->log_strategy_->generate_file_period_ == GP_DAILY)
		{
			this->last_calc_dtime_.mday (this->last_calc_dtime_.mday () + 
					this->log_strategy_->period_count_);
			this->last_calc_dtime_.hour (0);
		}else
		{
			// 3. hourly
			if (this->log_strategy_->generate_file_period_ == GP_HOURLY)
			{
				this->last_calc_dtime_.hour (this->last_calc_dtime_.hour () + 
						this->log_strategy_->period_count_);
			}
		}
	}

	this->last_calc_dtime_.min (0);
	this->last_calc_dtime_.sec (0);
	this->day_start_time_ = this->last_calc_dtime_.time ();
}
int LogToFiles::close (void)
{
	if (this->log_handle_ != NDK_INVALID_HANDLE)
		::close (this->log_handle_);
	this->log_handle_ = NDK_INVALID_HANDLE;
	return 0;
}
void LogToFiles::reset (void)
{
	// Build file name
	this->build_filename ();

	// Precalculate the time of the next file.
	this->calc_next_time ();

	this->close ();

	this->create_file ();
}
void LogToFiles::update_strategy (const char *log_dir,
		LogManager::Strategy *ls)
{
	if (this->old_log_strategy_ == 0)
		this->old_log_strategy_ = new LogManager::Strategy;
	*this->old_log_strategy_ = *this->log_strategy_;
	*this->log_strategy_ = *ls;
	if ((this->log_strategy_->generate_file_period_
				!= this->old_log_strategy_->generate_file_period_)
			||
			(this->log_strategy_->period_count_
			 != this->old_log_strategy_->period_count_)
			||
			(strncmp (this->log_strategy_->file_name_, 
					  this->old_log_strategy_->file_name_,
					  NDK_MAX_FILENAME_LEN) != 0)
			||
			(strncmp (this->log_dir_,
					  log_dir,
					  NDK_MAX_FILENAME_LEN) != 0)
	   )
	{
		strncpy (this->log_dir_, log_dir, NDK_MAX_FILENAME_LEN);
		this->reset ();
	}
}
int LogToFiles::log (const char *log_msg, int len)
{
	if (this->log_handle_ == NDK_INVALID_HANDLE)
		return -1;

	// 1. Check file size
	this->check_file_size ();

	// 2. Check a_file_a_day is on or not
	if (this->log_strategy_->generate_file_period_ != GP_DISABLE)
	{
		// Check time
		this->generate_new_file ();
	}

	// 3. Record the log msg
	int result = 0;
	if ((result = ::write (this->log_handle_, log_msg, len)) <= 0)
	{
		this->close ();
		return -1;
	}
	this->current_file_size_ += result;
	return result;
}
int LogToFiles::check_file_size (void)
{
	if (this->current_file_size_ >= this->log_strategy_->single_file_size_)
	{
		this->close ();

		// Backup file, switch file
		if (this->log_strategy_->max_rolloever_index_ > 0)
		{
			this->rolloever_files ();
			char tgt[NDK_MAX_FILENAME_LEN + 1] = {0};
			snprintf (tgt, NDK_MAX_FILENAME_LEN, 
					"%s.%d",
					this->file_name_,
					1);
			::rename (this->file_name_, tgt);
		}
		// Build file name
		//this->build_filename ();

		this->log_handle_ = ::open (this->file_name_, 
                O_RDWR | O_TRUNC | O_CREAT | O_APPEND, 
                0644);
#ifdef NDK_LOG_DEBUG
		fprintf (stderr, "Open file over file size: %s\n", 
				this->file_name_);
#endif
		if (this->log_handle_ == NDK_INVALID_HANDLE)
		{
			fprintf (stderr, "Open file failed : %s\n", 
					strerror (errno));
		}
		this->current_file_size_ = 0;
		return 1;
	}
	return 0;
}
int LogToFiles::rolloever_files ()
{
	char src[NDK_MAX_FILENAME_LEN + 1] = {0};
	char tgt[NDK_MAX_FILENAME_LEN + 1] = {0};
	for (int i = this->log_strategy_->max_rolloever_index_ - 1; 
			i >= 1; 
			--i)
	{
		snprintf (src, NDK_MAX_FILENAME_LEN, 
				"%s.%d",
				this->file_name_,
				i);
		snprintf (tgt, NDK_MAX_FILENAME_LEN, 
				"%s.%d",
				this->file_name_,
				i + 1);
		::rename (src, tgt);
	}
	return 0;
}
void LogToFiles::build_filename ()
{
	memset  (this->file_name_, '\0', NDK_MAX_FILENAME_LEN);
	strncat (this->file_name_, 
			LogManager::Output::log_dir_,
			NDK_MAX_FILENAME_LEN);
	strcat  (this->file_name_, "/");

	int pos = strlen (LogManager::Output::log_dir_) + 1;
	char date_str[32] = {0};
	char time_str[32] = {0};
	DateTime current_dtime;
	current_dtime.date_to_str(date_str, sizeof (date_str));
	if (strlen (this->log_strategy_->file_name_) == 0)
	{
		if (this->log_strategy_->generate_file_period_ == GP_HOURLY)
		{
			current_dtime.time_to_str (time_str, sizeof (time_str));
			snprintf (this->file_name_ + pos, NDK_MAX_FILENAME_LEN, 
					"%s-%s.log",
					date_str,
					time_str);
		}else
        {
            snprintf (this->file_name_ + pos, NDK_MAX_FILENAME_LEN, 
                    "%s.log",
                    date_str);
        }
	}else
	{
		if (this->log_strategy_->generate_file_period_ != GP_DISABLE)
		{
			switch (this->log_strategy_->generate_file_period_)
			{
				case GP_HOURLY:
					{
						current_dtime.time_to_str (time_str, sizeof (time_str));
						snprintf (this->file_name_ + pos, NDK_MAX_FILENAME_LEN,
								"%s-%s-%s.log",
								this->log_strategy_->file_name_,
								date_str,
								time_str);
						break;
					}
				case GP_DAILY:
				case GP_WEEKLY:
				default:
					{
						snprintf (this->file_name_ + pos, NDK_MAX_FILENAME_LEN,
								"%s-%s.log",
								this->log_strategy_->file_name_,
								date_str);
						break;
					}
			}
		}else
		{
            snprintf (this->file_name_ + pos, NDK_MAX_FILENAME_LEN,
                    "%s.log",
                    this->log_strategy_->file_name_);
		}
	}
}
void LogToFiles::generate_new_file ()
{
	if (difftime (time (0), 
				this->day_start_time_) >= 0)   // next period
	{
		this->build_filename ();
		int handle = ::open (this->file_name_, 
				O_CREAT | O_RDWR | O_APPEND, 0644);
#ifdef NDK_LOG_DEBUG
		fprintf (stderr, "Open file over time %lu - %lu : %s\n", 
				time (0), 
				this->day_start_time_,
				this->file_name_);
#endif
		if (handle != NDK_INVALID_HANDLE) // create file failed
		{
			::close (this->log_handle_);  // close prev log-handle
			this->log_handle_ = handle; //
		}else
		{
			fprintf (stderr, "Open file error : %s\n",
					strerror (errno));
		}
		this->calc_next_time ();
	}
}


